#include "FileServer.h"

#include "FileShare.h"
#include "OnlineUsers.h"

#include "file/FileTransmission.h"

#include "lib/signals/SignalHandler.h"

#include "lib/Semaforo.h"

#include "util/util.h"
#include "util/definitions.h"
#include "util/Log.h"

#include <stdlib.h>
#include <fstream>
#include <iostream>
#include <string.h>
#include <stdint.h>
#include <wait.h>
#include <fcntl.h>

FileServer::FileServer(int userID)
{
	myUserID = userID;

	SignalHandler::getInstance().bloquearTodasSeniales();

	SignalHandler::getInstance().desbloquearSenial(SIGTERM);
	SignalHandler::getInstance().desbloquearSenial(SIGINT);
	SignalHandler::getInstance().desbloquearSenial(SIGPIPE);

	SignalHandler::getInstance().registrarHandler(SIGTERM,&sigTerm);
	SignalHandler::getInstance().registrarHandler(SIGINT,&sigTerm);
	SignalHandler::getInstance().registrarHandler(SIGPIPE,&sigPipe);

	/* me aseguro que el archivo de semaforos este abierto */
	int sem_id = open(getSemaphoreKeyFile(myUserID).c_str(),O_CREAT,0777);
	close(sem_id);
}

FileServer::~FileServer() {
	children.clear();
}


bool FileServer::sendFile(Fifo &fileTransfer,std::string & fileName)
{
	FileTransmission uploader;
	Log::getInstance().log(myUserID, ">> subiendo archivo ");
	return uploader.uploadFile(fileTransfer,fileName);
}

bool FileServer::receiveFile(Fifo &fileTransfer,std::string & fileName)
{
	FileTransmission downloader;
	Log::getInstance().log(myUserID, ">> esperando descarga ");
	return downloader.downloadFile(fileTransfer,fileName);
}


bool FileServer::requestDownload(std::string & fileName, int userID)
{
	bool accepted = false;

	if (userID != myUserID && fileName.size() > 0)
	{
		Fifo request(getPathRequests(userID).c_str());
		Semaforo ready( getSemaphoreKeyFile(userID).c_str(), 'R',0);
		Semaforo complete( getSemaphoreKeyFile(userID).c_str(), 'C',0);

		int sent = 0;

		uint32_t myID = myUserID;
		uint32_t length = fileName.length();

		int bufferLen = length + 8;

		/* put the request inside the buffer */
		char* buffer = new char[bufferLen];

		memcpy(buffer,(char*)&myID, 4);					/* 4 bytes of my user id */
		memcpy(buffer + 4, (char*)&length, 4);			/* 4 bytes of path length */
		memcpy(buffer + 8, fileName.c_str(), length);	/* length bytes of file path */

		Log::getInstance().log(myUserID,"Esperando al servidor");

		int result = ready.wait();
		if (result != -1)	/* we wait until the server is ready to accept requests */
		{
			Log::getInstance().log(myUserID,"Servidor listo");

			/* Send the request through the FIFO.
			 * Only one process will write on the FIFO at the same time */
			sent = request.escribir(buffer,bufferLen);

			if (sent > 0)
				accepted = true;	/* request sent successfully :) */
			else
				Log::getInstance().log(myUserID,"No se ha podido enviar el request");
		}

		delete buffer;

		request.cerrar();
		complete.signal();
	}

	return accepted;
}

bool FileServer::downloadFile(std::string & fileName, int userID)
{
	bool result = false;

	if ( userID != myUserID)
	{
		Log::getInstance().log(myUserID, " Solicitar descarga a " + userID);

		result = requestDownload(fileName, userID);

		if (result)
		{
			Log::getInstance().log(myUserID,"mi request fue enviado exitosamente");
			Log::getInstance().log(myUserID,"ahora espero que me envien el archivo");

			/* create the FIFO for the download transfer */
			Fifo download( getPathDownload(userID,myUserID,fileName).c_str() );

			result = receiveFile(download, fileName);		/* this blocks the process until the transfer is complete or cancelled */

			Log::getInstance().log(myUserID,"fin de transferencia de archivo");

			download.cerrar();
		}
		else
			Log::getInstance().log(myUserID,"Solicitud cancelada. Error en la comunicacion con el servidor");
	}

	return result;
}

void FileServer::processRequest(int userID, std::string &fileName)
{
	if ( userID != myUserID )
	{
		Log::getInstance().log(myUserID, " Procesando request de " + userID);

		FileShare sharer(myUserID);
		int pos = sharer.findMySharedFile(fileName);	/* check that i own the requested file */

		if ( pos >= 0 )
		{
			std::clog<<myUserID<<": request aceptada a "<<userID<<" "<<fileName<<std::endl;

			/* create the file transfer FIFO */
			Fifo transfer( getPathDownload(myUserID,userID,fileName).c_str() );

			sendFile(transfer, fileName);

			transfer.cerrar();
		}
		else
			Log::getInstance().log(myUserID, " No se puede enviar el archivo porque no se esta compartiendo: " + fileName);
	}
}

bool FileServer:: listenToRequests(Fifo &requests)
{
	uint32_t userID = 0, length = 0;
	int read = 0;

	Log::getInstance().log(myUserID,"esperando request");

	/* wait for a request. FIFO is blocking so we will wait until someone opens the fifo for writes */

	read = requests.leer((char*) &userID, 4);	/* 4 bytes of user id */

	if ( read > 0 )
	{
		Log::getInstance().log(myUserID,"llego una request de archivo");

		read = requests.leer((char*) &length, 4);	/* 4 bytes of path length */

		if ( read > 0 )
		{
			char* path = new char[length+1];

			read = requests.leer(path,length);	/* length bytes of file path */

			path[length] = '\0';

			std::string file (path);

			delete path;

			if ( read > 0 )
			{
				/* process request */
				std::clog<<myUserID<<": procesar request: "<<userID<<" "<<length<<" "<<file<<std::endl;
				int pid = fork();
				if (pid == 0)
				{
					children.clear();
					processRequest(userID,file);
				}
				else
				{
					children.push_back(pid);	/* add the pid to the list of child process */
				}
			}
		}
	}

	if(read <= 0 && isRunning())
	{
		Log::getInstance().log(myUserID, " Error reading requests." + read);
	}

	return read > 0;
}

void FileServer::startServer()
{
	int masterPid = getpid();	//pid del server.
	bool ok = false;

	OnlineUsers userManager(myUserID);
	Fifo requests( getPathRequests(myUserID).c_str());	/* Fifo to wait for requests */
	Semaforo ready( getSemaphoreKeyFile(myUserID).c_str(), 'R',0);	/* semaphore to sync incoming users */
	Semaforo complete( getSemaphoreKeyFile(myUserID).c_str(), 'C',0);	/* semaphore to sync the end of the request */

	userManager.logIn();

	while( isRunning() && masterPid == getpid() )
	{
		ready.signal();		/* allow a user to send a request */

		ok = listenToRequests(requests);

		if ( ok && masterPid == getpid() )
		{
			Log::getInstance().log(myUserID," ------- esperando para cerrar el request");
			complete.wait();	/* wait until the other end closes the fifo */

			requests.cerrar(true);
		}
		else
			requests.cerrar(false);

		Log::getInstance().log(myUserID,"---------cerre el request");
	}

	if ( masterPid == getpid() )	/* only the original server process is allowed to logout */
	{
		Log::getInstance().log(myUserID,"cerrando server");

		requests.cerrar();

		userManager.logOut();

		ready.eliminar();
		complete.eliminar();

		finishSendingFiles();	/* wait until all files are sent */

		Log::getInstance().log(myUserID,"server cerrado ");

	}
}

void FileServer::finishSendingFiles()
{
	while (!children.empty())
	{
		Log::getInstance().log(getpid(), " Esperando fin del proceso " + children.front());
		waitpid(children.front(),NULL,0);
		children.pop_front();
	}
}

void FileServer::stopServer(int serverPid)
{
	SignalHandler::emitirSenial(serverPid,SIGTERM);
}

bool FileServer::isRunning()
{
	return (! sigTerm.getGracefulQuit());
}

